#pragma once
#include <struct/query_context.hpp>
#include <data/data_result.hpp>

namespace blitz_query_cpp
{
    inline const syntax_node *get_root_selection_node(query_context &context)
    {
        if (!context.document.operation)
        {
            context.report_error("Operation is not defined");
            return nullptr;
        }
        if (!context.document.operation->selection_set)
        {
            context.report_error("Root selection set is missing");
            return nullptr;
        }
        if (context.document.operation->selection_set->children.size() != 1)
        {
            context.report_error("Query should have only one selection set");
            return nullptr;
        }

        auto root_selection_field = context.document.operation->selection_set->children[0];
        if (!root_selection_field->selection_set || root_selection_field->selection_set->children.size() == 0)
        {
            context.report_error("Selection set can not be empty");
            return nullptr;
        }
        return root_selection_field;
    }

    inline const field *get_root_selection_field(query_context &context, std::string_view filed_name)
    {
        auto query_type = context.schema->get_query_type();
        if (!query_type)
        {
            context.report_error("Query type is not defined in schema");
            return nullptr;
        }

        auto root_selection_field = query_type->fields.find(filed_name);
        if (root_selection_field == query_type->fields.end())
        {
            context.report_error("Field '{}' is not declared in type '{}'", filed_name, query_type->name);
            return nullptr;
        }

        if (root_selection_field->field_type.type == nullptr)
        {
            context.report_error("Type of field '{}' in object '{}' is not defined", filed_name, query_type->name);
            return nullptr;
        }
        return &*root_selection_field;
    }

    template <class DelegateT, class PassThrough>
    PassThrough visit_fields_recursive(const syntax_node *selection_set, const object_type *type, PassThrough pass_through, DelegateT callback)
    {
        std::unordered_set<std::string_view> visited;

        for (const syntax_node *field_node : selection_set->children)
        {
            auto field_def_it = type->fields.find(field_node->name);
            const field *field_def = field_def_it == type->fields.end() ? nullptr : &*field_def_it;

            pass_through = callback(field_node, field_def, pass_through);
            visited.insert(field_node->name);

            if (field_def && field_def->field_type.of_kind(type_kind::Interface | type_kind::Object | type_kind::Union))
            {
                pass_through = visit_fields_recursive(field_node->selection_set, field_def->field_type.type, pass_through, callback);
            }
        }

        for (auto &&field_def : type->fields)
        {
            if (!visited.contains(field_def.name) && always_projected(field_def))
            {
                pass_through = callback(nullptr, &field_def, pass_through);
            }
        }
        return pass_through;
    }

    inline bool check_introspection(query_context &context)
    {
        if (!context.schema->get_type_type())
        {
            return context.report_error("Introspection types are not found in schema");
        }
        return true;
    }

    inline data_result_t& get_data_result(query_context &context)
    {
        auto &res = context.objects.try_emplace("data_result", data_result_t{}).first->second;
        data_result_t & root_node = std::any_cast<data_result_t&>(res);
        return root_node;
    }
}